classdef ziSpectrum < EquipmentControl.ZI.ziModule.ziModule
    %ZISPECTRUM implements the spectrum module of zi device
    %   example: iSpectrum('dev3546', session)
    
    properties
        available_fft_sample    % fft node names
    end
    
    properties (Access=private)
        channel_name % either 'deomds', or 'pids'
    end
    
    methods
        function obj = ziSpectrum(dev)
            %ZISPECTRUM Construct an instance of ziSpectrum module
            %   example: iSpectrum('dev3546', session)
            obj@EquipmentControl.ZI.ziModule.ziModule('spectrum', dev);
            obj.available_fft_sample = {'sample.AuxIn0.fft.abs.avg', ...
                                      'sample.AuxIn1.fft.abs.avg', ...
                                      'sample.Dio.fft.abs.avg', ...
                                      'sample.Frequency.fft.abs.avg', ...
                                      'sample.R.fft.abs.avg', ...
                                      'sample.Theta.fft.abs.avg', ...
                                      'sample.X.fft.abs.avg', ...
                                      'sample.XiY.fft.abs.filter', ...
                                      'sample.XiY.fft.abs.avg', ...
                                      'sample.Y.fft.abs.avg', ...
                                      'sample.df.fft.abs.avg', ...
                                      'stream/shift.val.fft.abs.avg', ...
                                      'stream/error.val.fft.abs.avg', ...
                                      'stream/value.val.fft.abs.avg' };
            obj.setting_name_readonly = {'buffercount', 'buffersize', 'triggered'};

        end
        
        %% Set Parameters
        function obj = setControl(obj, varargin)
            % set Control parameters
            p = inputParser;
            
            p.addParameter('type', 0);                              % trigger type: 
                                                                    % 0 = trigger off; 
                                                                    % 1 = analog edge trigger on source
                                                                    % 2 = digital trigger mode on DIO source
                                                                    % 3 = analog pulse trigger on source
                                                                    % 4 = analog tracking trigger on source
                                                                    % 5 = change trigger
                                                                    % 6 = hardware trigger on trigger line source
                                                                    % 7 = tracking edge trigger on source
                                                                    % 8 = event count trigger on counter source
            p.addParameter('preview', 1);                           % When enabled, allows the data of an incomplete trigger to be read. Useful for long data acquisitions/FFTs to display the progress.
            p.addParameter('historylength', 1);                     % Sets an upper limit for the number of data captures stored in the module.
            p.addParameter('endless', 0);                           % Enable endless triggering: 1=enable; 0=disable.
            
            p.addParameter('spectrum_enable', 1);                   % Enables the FFT mode of the data Acquisition module, in addition to time domain.
            p.addParameter('spectrum_frequencyspan', 1e3);          % Sets the desired frequency span of the FFT.
            p.addParameter('grid_rows', 1);                         % Specify the number of rows in the grid's matrix. Each row is the data recorded from one trigger mapped onto the columns.
            p.addParameter('grid_cols', 1024);                      % Specify the number of columns in the grid's matrix. The data from each row is mapped onto the grid according to the grid/mode setting with the specified number of columns.
            p.addParameter('grid_repetitions', 100);                % Number of statistical operations performed per grid.
            p.addParameter('fft_window', 1);                        % FFT window (default 1 = Hann)
                                                                    % 0 = Rectangular
                                                                    % 1 = Hann
                                                                    % 2 = Hamming
                                                                    % 3 = Blackman Harris 4 term
            p.addParameter('fft_absolute', 1);                      % Shifts the frequencies so that the center frequency becomes the demodulation frequency rather than 0 Hz.

            p.parse(varargin{:});
            
            obj.setParam('type', p.Results.type); 
            obj.setParam('preview', p.Results.preview); 
            obj.setParam('historylength', p.Results.historylength); 
            obj.setParam('endless', p.Results.endless); 
            
            obj.setParam('spectrum/enable', p.Results.spectrum_enable); 
            obj.setParam('spectrum/frequencyspan', p.Results.spectrum_frequencyspan); 
            obj.setParam('grid/rows', p.Results.grid_rows); 
            obj.setParam('grid/cols', p.Results.grid_cols); 
            obj.setParam('grid/repetitions', p.Results.grid_repetitions); 
            obj.setParam('fft/window', p.Results.fft_window); 
            obj.setParam('fft/absolute', p.Results.fft_absolute); 
        end
        
        %% Run
        function res = run(obj, varargin)
            % run the module
            clear textprogressbar;
            
            p=inputParser;
            func_hdl_test = @(x) isa(x, 'function_handle') || strcmp(x, 'none');
            p.addParameter('processMonitor', @plotData, func_hdl_test);
            p.parse(varargin{:});
            
            if ~isvalid(obj.monitor_hdl)
                obj.createMonitor;
            end
            
            processMonitor = p.Results.processMonitor;
            obj.monitor_plot_init();
            
            tStart = tic;
            obj.executeModule();
            textprogressbar( sprintf('%s Acquiring Data,\t', datestr(now,'yyyy-mm-dd HH:MM:SS,FFF')) );
            while ~obj.finishedModule()
                pause(0.5);
                textprogressbar(obj.progressModule()*100);
                
                tmp = obj.read();
                if ziCheckPathInData(tmp, obj.subscribed_paths{1}.path_channel_string)
                    spectData = EquipmentControl.ZI.ziData.ziSpectrumData(tmp, 'channel_name', obj.channel_name);
                    data = arrayfun(@(curve) spectData.getNodeData(curve.channel, curve.node, 'sampleIndex', 1), obj.curve_info);
                    processMonitor(obj, obj.monitor_hdl, data);
                end
                
            end
            res = EquipmentControl.ZI.ziData.ziSpectrumData(tmp, 'channel_name', obj.channel_name);
            textprogressbar( 100 ); textprogressbar(['.\t done. [Time used: ' num2str(toc(tStart)) ' seconds]']);

            tmp = obj.read();
            if ziCheckPathInData(tmp, obj.subscribed_paths{1}.path_channel_string)
                spectData = EquipmentControl.ZI.ziData.ziSpectrumData(tmp, 'channel_name', obj.channel_name);
                data = arrayfun(@(curve) spectData.getNodeData(curve.channel, curve.node, 'sampleIndex', 1), obj.curve_info);
                processMonitor(obj, obj.monitor_hdl, data);
                res = spectData;
            end
            
            obj.measured_data = res;
            obj.finish();
        end        
        
        %% Subscribe/Unsubscribe node
        function obj = subscribeChannel(obj, channel, sample_names, varargin)
            % subscribe channels
            L=log4m.getLogger;
            p = inputParser;
            chTest = @(x) ismember(channel, obj.available_demod_channel);
            sampleTest = @(x) all(ismember(sample_names, obj.available_fft_sample));
            p.addRequired('channel', chTest);
            p.addRequired('sample_name', sampleTest);
            p.addParameter('channel_name', 'demods', @(x) ismember(x, {'demods', 'pids'}) );
            p.parse(channel, sample_names, varargin{:});
            
            obj.channel_name = p.Results.channel_name;
            
            obj.zi.enableDataTransfer(channel);
            paths = obj.make_path(channel, sample_names, obj.channel_name);

            channelPath = paths{1}.path_string;
            obj.subscribeNode(channelPath);
            L.info('ziSpectrum::subscribeChannel', sprintf('Node %s subscribed.', channelPath));
        end
        
        function obj = unsubscribeChannel(obj)
            % unsubscribe channels
            L=log4m.getLogger;
            subs_ch = unique( cellfun(@(p) p.path_string ,obj.subscribed_paths, 'UniformOutput', false) );
            for k = 1:length(subs_ch)
              obj.unsubscribeNode(subs_ch{k});
              L.info('ziSpectrum::unsubscribeChannel', sprintf('Node %s unsubscribed.', subs_ch{k}));
            end
            obj.syncModule();
        end

%% monitor 
        function plotData(obj, ~, data)
            % default monitor plot function
            for k=1:length(obj.curve_info)
                obj.curves(k).XData = data(k).freq;
                obj.curves(k).YData = data(k).value;
            end
            drawnow limitrate            
        end
        
        function monitor_plot_init(obj)
            % monitor plot initialization
            strucParam = obj.getParam('spectrum/frequencyspan');
            maxF = 0.5 * strucParam.spectrum.frequencyspan;
            xlim(obj.monitor_hdl, [0 maxF]);
            
            color_array = obj.monitor_hdl.ColorOrder;
            len_color = size(color_array, 1);
            obj.curves = arrayfun(@(k) line(obj.monitor_hdl, ...
                                           'Color', color_array(mod(k, len_color)+1,:), ...
                                           'DisplayName', obj.curve_info(k).str), ...
                                 1:length(obj.curve_info));
            obj.monitor_hdl.YScale = 'log';
            obj.monitor_hdl.XScale = 'log';
            legend(obj.monitor_hdl, 'show');
            grid(obj.monitor_hdl, 'on');
        end        
    end
    %% private
    methods (Access = private)
        function paths = make_path(obj, ch, sample, channel_name)
            if ~iscell(sample)
                sampleCell = {sample};
            else
                sampleCell = sample;
            end
            
            paths = cellfun(@(sa) EquipmentControl.ZI.ziData.ziPath(obj.device, channel_name, ch,  sa, ''), sampleCell, 'UniformOutput', false);
            obj.subscribed_paths = [obj.subscribed_paths paths];
            obj.curve_info=cellfun(@(p) p.channel_sample_str, obj.subscribed_paths);
        end
    end
end

